package com.ophiux.auth.controller;

import cn.hutool.core.util.StrUtil;
import com.ophiux.auth.support.handler.AuthenticationFailureEventHandler;
import com.ophiux.common.core.utils.RetOps;
import com.ophiux.common.core.constant.CacheConstants;
import com.ophiux.common.core.constant.SecurityConstants;
import com.ophiux.common.core.exception.APIException;
import com.ophiux.common.core.utils.R;
import com.ophiux.common.core.utils.SpringContextHolder;
import com.ophiux.system.api.feign.RemoteOauthClientDetailsService;
import com.ophiux.system.api.feign.RemoteTenantService;
import com.ophiux.system.model.entity.SysOauthClientDetails;
import com.ophiux.system.model.entity.SysTenant;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.Principal;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @desc: 删除token端点
 * @author: hhl
 * @date：2023/7/21 10:03
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/token")
public class OphiuxTokenEndpoint {
    @Autowired
    private AuthenticationFailureEventHandler authenticationFailureHandler;

    @Autowired
    private OAuth2AuthorizationService authorizationService;
    @Autowired
    private RemoteOauthClientDetailsService clientDetailsService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

	private final RemoteTenantService remoteTenantService;

    //	private final KeyStrResolver tenantKeyStrResolver;
    @Autowired
    private CacheManager cacheManager;

    /**
     * 认证页面
     * @param modelAndView
     * @param error        表单登录失败处理回调的错误信息
     * @return ModelAndView
     */
    @GetMapping("/login")
    public ModelAndView login(ModelAndView modelAndView, @RequestParam(required = false) String error) {
        modelAndView.setViewName("login");
        modelAndView.addObject("error", error);

		R<List<SysTenant>> tenantListResult = remoteTenantService.list(SecurityConstants.FROM_IN);
        modelAndView.addObject("tenantList", tenantListResult.getData());
        return modelAndView;
    }

    /**
     * 访问 http://localhost:8200/oauth2/authorize?scope=server&client_id=open&response_type=code&redirect_uri=https://www.baidu.com  跳转登录界面，登录成功后，访问界面
     * @param principal
     * @param modelAndView
     * @param clientId
     * @param scope
     * @param state
     * @return
     */
    @GetMapping("/confirm_access")
    public ModelAndView confirm(Principal principal, ModelAndView modelAndView, @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                                @RequestParam(OAuth2ParameterNames.SCOPE) String scope, @RequestParam(OAuth2ParameterNames.STATE) String state) {
        SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId, SecurityConstants.FROM_IN)).getData().orElseThrow(() -> new APIException("clientId 不合法"));

        Set<String> authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());
        modelAndView.addObject("clientId", clientId);
        modelAndView.addObject("state", state);
        modelAndView.addObject("scopeList", authorizedScopes);
        modelAndView.addObject("principalName", principal.getName());
        modelAndView.setViewName("confirm");
        return modelAndView;
    }

    /**
     * 退出并删除token
     *
     * @param authHeader Authorization
     */
    @DeleteMapping("/logout")
    public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
        if (StrUtil.isBlank(authHeader)) {
            return R.ok();
        }

        String tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim();
        return removeToken(tokenValue);
    }

    /**
     * 校验token
     *
     * @param token 令牌
     * @return
     */
    @SneakyThrows
    @GetMapping("/check_token")
    public R<OAuth2AccessToken> checkToken(String token, HttpServletResponse response, HttpServletRequest request) {
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

        if (StrUtil.isBlank(token)) {
            httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
            this.authenticationFailureHandler.onAuthenticationFailure(request, response, new InvalidBearerTokenException("令牌不存在"));
            return R.failed();
        }
        OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);

        // 如果令牌不存在 返回401
        if (authorization == null || authorization.getAccessToken() == null) {
            this.authenticationFailureHandler.onAuthenticationFailure(request, response, new InvalidBearerTokenException("不合法的Token"));
        }

        // 获取令牌
        return R.ok(Objects.requireNonNull(authorization).getAccessToken().getToken());
    }

    /**
     * 令牌管理调用
     *
     * @param token token
     */
//	@Inner
    @DeleteMapping("/{token}")
    public R<Boolean> removeToken(@PathVariable("token") String token) {
        OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        if (authorization == null) {
            return R.ok();
        }

        OAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();
        if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) {
            return R.ok();
        }
        // 清空用户信息
        cacheManager.getCache(CacheConstants.USER_DETAILS).evict(authorization.getPrincipalName());
        // 清空access token
        authorizationService.remove(authorization);
        // 处理自定义退出事件，保存相关日志
        SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(authorization.getPrincipalName(), authorization.getRegisteredClientId())));
        return R.ok();
    }


    //	@Inner
    @GetMapping("/query-token")
    public R queryToken(String token) {
        OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        return R.ok(authorization);
    }

}
